package org.dairy.farms.dao.persistence;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dairy.farms.dao.exception.PersistenceException;
import org.hibernate.*;
import org.perf4j.aop.Profiled;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.*;

import java.io.Serializable;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: gduggira
 * Date: Jun 9, 2011
 * Time: 5:48:51 PM
 */
public class BasePersistenceImpl<T> implements BasePersistence<T> {
    private static final Log logger = LogFactory.getLog(BasePersistenceImpl.class);

    private HibernateTemplate hibernateTemplate = null;

    public Session getSession() {
        return SessionFactoryUtils.getSession(hibernateTemplate.getSessionFactory(), false);
    }

    @Profiled(tag = "BPI->find", message = "query was {$0}.")
    public <T> List<T> find(String query) throws PersistenceException {
        try {
            return hibernateTemplate.find(query);
        } catch (DataAccessException e) {
            logger.error("Error finding the object", e);
            throw new PersistenceException(e);
        }
    }

    @Profiled(tag = "BPI->findWithParams", message = "query was {$0}, params were ${1}")
    public <T> List<T> find(String query, Object[] params) throws PersistenceException {
        try {
            return hibernateTemplate.find(query, params);
        } catch (DataAccessException e) {
            logger.error("Error finding the object", e);
            throw new PersistenceException(e);
        }
    }

    @Profiled(tag = "BPI->findWithParam", message = "query was {$0}, params were ${1}")
    public <T> List<T> find(String query, Object param) throws PersistenceException {
        try {
            return hibernateTemplate.find(query, param);
        } catch (DataAccessException e) {
            logger.error("Error finding the object", e);
            throw new PersistenceException(e);
        }
    }

    @Profiled(tag = "BPI->loadAll", message = " class ${0}")
    public <T> List<T> loadAll(Class<T> clazz) throws PersistenceException {
        try {
            return hibernateTemplate.loadAll(clazz);
        } catch (DataAccessException e) {
            logger.error("Error loading the object" ,e);
            throw new PersistenceException(e);
        }
    }

    @Profiled(tag = "BPI->load", message = " id = ${0} of class ${1}")
    public <T> T load(Class<T> clazz, Serializable id) throws PersistenceException {
        try {
            T object =  (T) hibernateTemplate.load(clazz, id);
            Hibernate.initialize(object);
            return object;
        } catch (HibernateObjectRetrievalFailureException e) {
            //TODO: This should bettter be a different exception, so that it can be dealt with differently.
            //Let's make clear the intention of the exception so that the client does not have to deal with
            //one general exception and then decide what can be done,
            //If something is recoverable, then throw a different exception, if something is special throw a different
            //one.
            throw new PersistenceException(e);
        } catch (DataAccessException e) {
            logger.error("Error loading the object", e);
            throw new PersistenceException(e);
        }
    }

    @Profiled(tag = "BPI->loadWithParam", message = " id = ${0} of class ${1}")
    public <T> T load(String query, Object param) throws PersistenceException {
        List results = find(query, param);
        if (results.size() != 1) {
            //Same Exception principle.
            throw new PersistenceException("Could not load unique object");
        }
        return (T) results.get(0);
    }

    public <T> T load(String query, Object[] params) throws PersistenceException {
        List results = find(query, params);
        if (results.size() != 1) {
            throw new PersistenceException("Could not load unique object");
        }
        return (T) results.get(0);
    }

    @Profiled(tag = "BPI->save", message = "{$0}")
    public void save(final Object obj) throws PersistenceException {
        execute(new HibernateCallback() {
            public Object doInHibernate(Session sess) throws HibernateException {
                try {
                    sess.save(obj);
                    sess.flush();
                }
                catch (PersistenceException pe) {
                    logger.error(pe);
                    throw new PersistenceException("Could not save the object");
                }
                catch (HibernateException he) {
                    logger.error(he);
                    throw new HibernateException("Could not save the object", he);
                }
                return null;
            }
        });
    }

    @Profiled(tag = "BPI->update", message = "{$0}")
    public void update(final Object obj) throws PersistenceException {
        try {
            execute(new HibernateCallback() {
                public Object doInHibernate(Session sess) throws HibernateException {
                    sess.update(obj);
                    sess.flush();
                    return null;
                }
            });
        } catch (PersistenceException e) {
            Throwable cause = e.getCause();
            if (cause != null && cause instanceof HibernateOptimisticLockingFailureException) {
                //TODO: Handle potential staleObject?
                logger.debug("stale object.");
            }
            throw e;
        }
    }

    @Profiled(tag = "BPI->updateOrDelete", message = "{$0}")
    public int updateOrDelete(final String query) throws PersistenceException {
        Integer result;
        try {
            result = (Integer) execute(new HibernateCallback() {
                public Object doInHibernate(Session sess) throws HibernateException {
                    SQLQuery sqlQuery = sess.createSQLQuery(query);
                    return sqlQuery.executeUpdate();
                }
            });
        } catch (PersistenceException e) {
            Throwable cause = e.getCause();
            if (cause != null && cause instanceof HibernateOptimisticLockingFailureException) {
                //TODO: Handle potential staleObject?
                logger.debug("stale object.");
            }
            throw e;
        }
        return result;
    }

    @Profiled(tag = "BPI->delete", message = "{$0}")
    public void delete(final Object obj) throws PersistenceException {
        execute(new HibernateCallback() {
            public Object doInHibernate(Session sess) throws HibernateException {
                sess.delete(obj);
                sess.flush();
                return null;
            }
        });
    }

    @Profiled(tag = "BPI->refresh", message = "{$0}")
    public void refresh(final Object obj) throws PersistenceException {
        execute(new HibernateCallback() {
            public Object doInHibernate(Session sess) throws HibernateException {
                sess.refresh(obj);
                return obj;
            }
        });
    }

    public <T> T execute(HibernateCallback callback) throws PersistenceException {
        try {
            return (T) hibernateTemplate.execute(callback);
        } catch (DataAccessException e) {
            throw new PersistenceException(e);
        }
    }

    public <T> T executeFind(HibernateCallback callback) throws PersistenceException {
        try {
            return (T) hibernateTemplate.executeFind(callback);
        } catch (DataAccessException e) {
            throw new PersistenceException(e);
        }
    }

    @Override
    public <T> List<T> findByNamedQueryAndNamedParam(String queryName, String paramName, Object value) {
        return hibernateTemplate.findByNamedQueryAndNamedParam(queryName,paramName,value);
    }

    @Override
    public <T> List<T> findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values) {
       return hibernateTemplate.findByNamedQueryAndNamedParam(queryName,paramNames,values);
    }

    @Override
    public <T> T get(Class<T> clazz, Serializable id) {
        return hibernateTemplate.get(clazz,id);
    }

    public <T> List<T> findByNamedParam(String query, String paramName, Object value) throws PersistenceException {
        return hibernateTemplate.findByNamedParam(query, paramName, value);
    }

    public <T> List<T> findByNamedParams(String query, String[] paramNames, Object[] values) throws PersistenceException {
        return hibernateTemplate.findByNamedParam(query, paramNames, values);
    }

    public <T> List<T> findByNamedQuery(String query) throws PersistenceException {
        return hibernateTemplate.findByNamedQuery(query);
    }

    public <T> List<T> findByNamedQuery(String query, Object value) throws PersistenceException {
        return hibernateTemplate.findByNamedQuery(query, value);
    }

    public <T> List<T> findByNamedQuery(String query, Object[] values) throws PersistenceException {
        return hibernateTemplate.findByNamedQuery(query, values);
    }

    public <T> List<T> findByProperty(Class clazz, String propertyName, Object value) {

        StringBuffer query = new StringBuffer();
        query.append("FROM ")
                .append(clazz.getName())
                .append(" as domainObject where domainObject.")
                .append(propertyName)
                .append(" = :")
                .append(propertyName);
        if (logger.isDebugEnabled()) {
            logger.debug(query);
        }

        return hibernateTemplate.findByNamedParam(query.toString(), propertyName, value);
    }

    public List findByValueBean(final String queryString, final Object valueBean)
            throws PersistenceException {
        return executeFind(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Query queryObject = session.createQuery(queryString);
                queryObject.setProperties(valueBean);
                return queryObject.list();
            }
        });
    }

    protected HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }
}
